如果你不想使用调试器,你可以按照下面的一些有用的方法来达到基本调试的目的:
- 在合适的位置使用打印语句输出相关变量的值(
print/println和fmt.Print/fmt.Println/fmt.Printf)。
- 在合适的位置使用打印语句输出相关变量的值(
- 在
fmt.Printf中使用下面的说明符来打印有关变量的相关信息:%+v打印包括字段在内的实例的完整信息%#v打印包括字段和限定类型名称在内的实例的完整信息%T打印某个类型的完整说明
- 在
- 使用
panic语句来获取栈跟踪信息(直到panic时所有被调用函数的列表)。
- 使用
- 使用关键字
defer来跟踪代码执行过程。
- 使用关键字
_本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。- 你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:
package main。package main表示一个可独立执行的程序,每个Go应用程序都包含一个名为main的包。 - 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:
Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的private)。
(大写字母可以使用任何Unicode编码的字符,比如希腊文,不仅仅是ASCII码中的大写字母)。
因此,在导入一个外部包后,能够且只能够访问该包中导出的对象。 main函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有init()函数则会先执行该函数)。如果你的main包的源代码没有包含 main 函数,则会引发构建错误undefined: main.main。main 函数既没有参数,也没有返回类型(与 C 家族中的其它语言恰好相反)。如果你不小心为 main 函数添加了参数或者返回类型,将会引发构建错误:func main must have no arguments and no return values results.Go语言中不存在类型继承。
函数也可以是一个确定的类型,就是以函数作为返回类型。这种类型的声明要写在函数名和可选的参数列表之后,例如:1func FunctionName (a typea, b typeb) typeFunc一个函数可以拥有多返回值,返回类型之间需要使用逗号分割,并使用小括号 () 将它们括起来,如:
1func FunctionName (a typea, b typeb) (t1 type1, t2 type2)
多返回值一般用于判断某个函数是否执行成功(true/false)或与其它返回值一同返回错误消息(详见之后的并行赋值)。
Go语言不存在隐式类型转换,因此所有的转换都必须显式说明,就像调用一个函数一样(类型在这里的作用可以看作是一种函数):1valueOfTypeB = typeB(valueOfTypeA)
类型 B 的值 = 类型 B(类型 A 的值)
示例:
反斜杠
\可以在常量表达式中作为多行的连接符使用12const Ln2= 0.693147180559945309417232121458\176568075500134360255254120680009iota可以被用作枚举值:12345const (a = iotab = iotac = iota)
第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:
iota 也可以用在表达式中,如:iota + 50。在每遇到一个新的常量块或单个常量声明时, iota 都会重置为 0(简单地讲,每遇到一次 const ,iota 就重置为 0)。
- 指针的一个高级应用是你可以传递一个变量的引用(如函数的参数),这样不会传递变量的拷贝。指针传递是很廉价的,只占用 4 个或 8 个字节。当程序在工作中需要占用大量的内存,或很多变量,或者两者都有,使用指针会减少内存占用和提高效率。被指向的变量也保存在内存中,直到没有任何指针指向它们,所以从它们被创建开始就具有相互独立的生命周期。有点类似于Linux中的软连接
ln -s - 函数也可以以申明的方式被使用,作为一个函数类型,就像:1type binOp func(int, int) int
函数是一等值( first-class value):它们可以赋值给变量,就像 add := binOp 一样。
这个变量知道自己指向的函数的签名,所以给它赋一个具有不同签名的函数值是不可能的。
函数值(functions value)之间可以相互比较:如果它们引用的是相同的函数或者都是 nil 的话,则认为它们是相同的函数。函数不能在其它函数里面声明(不能嵌套),不过我们可以通过使用匿名函数来破除这个限制。
如果函数的最后一个参数是采用
...type的形式,那么这个函数就可以处理一个变长的参数,这个长度可以为 0,这样的函数称为变参函数。func myFunc(a, b, arg ...int) {}
这个函数接受一个类似某个类型的slice的参数,该参数可以通过for循环结构迭代。
示例函数和调用:12func Greeting(prefix string, who ...string)Greeting("hello:", "Joe", "Anna", "Eileen")如果变长参数的类型并不是都相同的呢?使用 5 个参数来进行传递并不是很明智的选择,有 2 种方案可以解决这个问题.
- 定义一个结构类型,假设它叫 Options,用以存储所有可能的参数:12345type Options struct {par1 type1,par2 type2,...}
函数 F1 可以使用正常的参数 a 和 b,以及一个没有任何初始化的 Options 结构:
F1(a, b, Options {})。如果需要对选项进行初始化,则可以使用F1(a, b, Options {par1:val1, par2:val2})。- 定义一个结构类型,假设它叫 Options,用以存储所有可能的参数:
- 使用空接口:
如果一个变长参数的类型没有被指定,则可以使用默认的空接口
interface{},这样就可以接受任何类型的参数(详见第 11.9 节)。该方案不仅可以用于长度未知的参数,还可以用于任何不确定类型的参数。一般而言我们会使用一个for-range循环以及switch结构对每个参数的类型进行判断:1234567891011func typecheck(..,..,values … interface{}) {for _, value := range values {switch v := value.(type) {case int: …case float: …case string: …case bool: …default: …}}}
关键字
defer允许我们推迟到函数返回之前(或任意位置执行return语句之后,或者函数执行到最后)一刻才执行某个语句或函数(为什么要在返回之后才执行这些语句?因为return语句同样可以包含一些操作,而不是单纯地返回某个值)。
当有多个defer行为被注册时,它们会以逆序执行(类似栈,即后进先出):12345func f() {for i := 0; i < 5; i++ {defer fmt.Printf("%d ", i)}}上面的代码将会输出:4 3 2 1 0。
- 一个字符串本质上是一个字节数组
从字符串生成字节切片,可以直接通过c := []byte(s)来获取一个字节的切片
通过代码len([]int32(s))来获得字符串中字符的数量,但使用utf8.RuneCountInString(s)效率会更高一点
未完待续…